import { ethers } from "ethers"
import callContract from '@/contract-files/callContract.json'
import tokenContract from '@/contract-files/tokenContract.json'
import { reactive } from "vue"
import { CatchError } from "@/utils/catch-error"
import type { Miner, MinerPosition } from "./type"
import { t } from "@/locale"
import Decimal from "decimal.js"

export class Config {
  static minerTableHeader = ["minerIndex", "hashrate", "powerConsumption", "cost", "inProduction"] as const
  /** 创建合约的地址 */
  static contractAddress = '0x6dcd7ec481371a59b5ea9d625ea25db54eedf9f0'
  /** 合约小数位 */
  static unit = 18
  /** 合约 */
  static #contract?: ethers.Contract

  static get contract() {
    if (!this.#contract) this.#contract = createContract(this.contractAddress)
    return this.#contract
  }
}

export const call = new Proxy({
  /** 获取当前货币的合约地址 */
  bigcoin(fun?: Function) {
    return fun?.() as string
  },
  /** 获取地块价格 */
  initialFacilityPrice(fun?: Function) {
    return fun?.() as ethers.BigNumber
  },
  /** 获取矿工当前产出数量 */
  async pendingRewards(fun?: Function) {
    const value = await fun?.(state.account) as ethers.BigNumber
    return new Decimal(value.toString()).div(10 ** Config.unit).toNumber()
  },
  /** 领取当前产出数量 */
  async claimRewards(fun?: Function) {
    try {
      await fun?.()
      await Promise.all([
        call.pendingRewards()
      ])
      CatchError.prompt(t('home.receive') + t('other.success'))
    } catch (error) {
      CatchError.prompt(t('home.receive') + t('other.fail'))
      throw error
    }
  },
  /** 获取土地信息 */
  async ownerToFacility(fun?: Function) {
    const value = await fun?.(state.account) as MinerPosition
    state.landPosition = []
    for (let row = 0; row < value.x.toNumber(); row++) {
      state.landPosition[row] = []
      for (let column = 0; column < value.y.toNumber(); column++) {
        const is = callValue.acquiredStarterMiner?.value
        const obj = is ? {
          text: t('home.purchase'),
          handle: (p: LandPosition) => {
            state.landPosition.forEach(list => list.forEach(item => item.isActive = false))
            p.isActive = !p.isActive
          }
        } : {
          text: t('home.receive') + t('home.free'),
          handle: call.getFreeStarterMiner
        }
        state.landPosition[row].push({ x: row, y: column, ...obj })
      }
      console.log(state.landPosition);
      
    }
    return value
  },
  /** 查看用户是否购买过初始地块 */
  initializedStarterFacility(fun?: Function) {
    return fun?.(state.account) as boolean
  },
  /** 购买初始地块 */
  async purchaseInitialFacility(fun?: Function) {
    try {
      await fun?.(
        state.inviterAccount,
        { value: callValue.initialFacilityPrice?.value }
      )
      await Promise.all([
        call.initializedStarterFacility(),
        call.ownerToFacility()
      ])
      CatchError.prompt(t('home.purchase') + t('other.success'))
    } catch (error) {
      CatchError.prompt(t('home.purchase') + t('other.fail'))
      throw error
    }
  },
  /** 查看用户是否领取过免费矿工 */
  acquiredStarterMiner(fun?: Function) {
    return fun?.(state.account) as boolean
  },
  /** 领取免费矿工 */
  async getFreeStarterMiner(position: Record<'x' | 'y', number>, fun?: Function) {
    try {
      uni.showLoading({ mask: true, title: t('home.receive') + t('home.center') })
      await fun?.(position.x, position.y)
      await Promise.all([
        call.ownerToFacility(),
        call.acquiredStarterMiner(),
        call.getPlayerMinersPaginated()
      ])
      CatchError.prompt(t('home.receive') + t('other.success'))
    } catch (error) {
      CatchError.prompt(t('home.receive') + t('other.fail'))
      uni.hideLoading()
      throw error
    }
  },
  /** 购买矿工 */
  async buyMiner(param: Pick<Miner, 'minerIndex'>, fun?: Function) {
    let position
    for (const list of state.landPosition) {
      position = list.find(item => item.isActive)
      if (position) break
    }
    try {
      if (!position) return CatchError.prompt(t('home.pleaseSelectPositionMiner'))

      // await fun?.(param.minerIndex, position.x, position.y)
      const contract = createContract(callValue.bigcoin?.value!)
      await contract.buyMiner?.(param.minerIndex, position.x, position.y)
      await Promise.all([
        call.ownerToFacility(),
        call.getPlayerMinersPaginated(),
        call.miners(),
      ])
      position.isActive = false
      CatchError.prompt(t('home.purchase') + t('other.success'))
    } catch (error) {
      CatchError.prompt(t('home.purchase') + t('other.fail'))
      throw error
    }
  },
  /** 获取矿工类别数量 */
  async uniqueMinerCount(fun?: Function) {
    const value = await fun?.() as ethers.BigNumber
    return value.toNumber()
  },
  /** 获取用户矿工列表 */
  async getPlayerMinersPaginated(fun?: Function) {
    const value = await fun?.(state.account, 0, 2) as Miner[]
    value.forEach(item => {
      const obj = state.landPosition[item.x.toNumber()][item.y.toNumber()]
      if (obj) obj.miner = {}
    })
    return value
  },
  /** 获取平台矿工列表 */
  miners(fun?: Function) {
    return Promise.all(
      Array.from({ length: callValue.uniqueMinerCount?.value! })
        .map((_, i) => fun?.(i + 1) as Miner)
    )
  },
}, {
  get(target, p, receiver) {
    const key = p as keyof typeof call
    return function (...args: Parameters<typeof call[typeof key]>) {
      return new Promise((resolve, reject) => {
        if (!callValue[key]) {
          callValue[key] = { loading: { value: false } }
          ce[key] = new CatchError({ loading: callValue[key].loading })
        }
        ce[key].run = async () => {
          const value = await target[key](...args as [any], Config.contract[key])
          callValue[key]!.value = value
          resolve(value)
          // console.log(value, p)
        }
        ce[key].fail = err => {
          console.error(err, p)
          reject(err)
        }
      })
    }
  },
})
export const callValue = reactive(<Partial<{
  [K in keyof typeof call]: {
    loading: { value: boolean }
    value?: Awaited<ReturnType<typeof call[K]>>
  }
}>>{})
const ce = <{ [K in keyof typeof call]: CatchError }>{}
type LandPosition = {
  x: number, y: number, text: string,
  handle: typeof call.getFreeStarterMiner | ((obj: LandPosition) => void),
  miner?: {},
  isActive?: boolean
}
export const state = reactive({
  account: '',
  landPosition: Array<Array<LandPosition>>(),
  inviterAccount: '0x3660fbc8362769a28a193786ffff1fcdd22db229'
})

/**
 * 连接链
 */
export async function connectNetWork() {
  if (!window.ethereum) return

  //BSC测试链
  const testChain = {
    chainId: "0x61",
    chainName: "BSC test",
    nativeCurrency: {
      name: "BNB",
      symbol: "tbnb",
      decimals: Config.unit,
    },
    rpcUrls: ["https://data-seed-prebsc-2-s3.binance.org:8545/"],
    blockExplorerUrls: ["https://testnet.bscscan.com/"],
  }
  const chain = testChain
  if (window.ethereum.chainId === chain.chainId) return

  await window.ethereum?.request?.({
    method: "wallet_addEthereumChain",
    params: [chain],
  })
  return init()
}

/**
 * 连接钱包
 */
export async function connectWallet() {
  try {
    if (!window.ethereum) return CatchError.prompt(t('home.pleaseInstallWallet'))
    uni.showLoading({ title: t('home.connectWallet') + t('home.center'), mask: true })
    const web3 = new ethers.providers.Web3Provider(window.ethereum)
    let accounts = await web3.provider.request?.({ method: 'eth_accounts' })

    if (!accounts?.length)
      accounts = await web3.provider.request?.({ method: 'eth_requestAccounts' })

    const message = `Welcome to ${t('home.welcome')}`

    await web3.getSigner().signMessage(message)
    await init()

    CatchError.prompt(t('home.connectWallet') + t('other.success'))
  } catch (error) {
    CatchError.prompt(t('home.connectWallet') + t('other.fail'))
    uni.hideLoading()
  }
}

/**
 * 创建合约
 */
function createContract(address: string, abi = callContract) {
  const web3 = new ethers.providers.Web3Provider(window.ethereum!)
  const tokenContract = new ethers.Contract(
    address,
    abi,
    web3.getSigner()
  )
  return tokenContract.connect(web3.getSigner())
}

/**
 * 授权当前代币
 */
async function authorizeToken() {
  const contract = createContract(callValue.bigcoin?.value!, tokenContract)
  const amount = ethers.utils.parseUnits('999999999999', Config.unit)
  const tx = await contract.approve(Config.contractAddress, amount)
  return tx.wait()
}

/**
 * 检查代币是否已授权
 */
export async function checkTokenAuthorization() {
  const contract = createContract(callValue.bigcoin?.value!, tokenContract)
  // 查询当前授权额度
  const allowance = await contract.allowance(
    state.account,           // 所有者（用户地址）
    Config.contractAddress   // 被授权者（挖矿合约地址）
  )
  // 将额度转换为可读格式
  const readableAllowance = new Decimal(allowance.toString()).div(10 ** Config.unit).toNumber()
  const requiredAmount = callValue.miners?.value?.reduce((p, c) => {
    let minerPrice
    try {
      minerPrice = c.cost.toNumber()
    } catch (error) {
      minerPrice = 0
    }
    if (p <= 0) p = minerPrice
    if (minerPrice > 0) return Math.min(p, minerPrice)
    return p
  }, 0) ?? 0
  // 判断是否有足够的授权额度
  return readableAllowance >= requiredAmount
}

/**
 * 初始化获取页面数据
 */
export async function init() {
  const account = window.ethereum?.selectedAddress

  if (!account) return

  state.account = account
  // 有些函数提前用到里里面的内容，所以需要按顺序加载
  await Promise.all([
    call.bigcoin(),
    call.acquiredStarterMiner(),
  ])
  console.log(1);
  
  await Promise.all([
    call.uniqueMinerCount(),
  ])
  console.log(2);
  
  await Promise.all([
    call.miners(),
  ])
  console.log(3);
  
  const isAuthToken = await checkTokenAuthorization()
  await Promise.all([
    call.initialFacilityPrice(),
    call.ownerToFacility(),
    call.pendingRewards(),
    call.initializedStarterFacility(),
    !isAuthToken && authorizeToken()
  ])
  console.log(4);
  
  await Promise.all([
    call.getPlayerMinersPaginated(),
  ])
  console.log(5);
  
}